package c.c.d.s.s.o.c.as.configuration.support.user;

import c.c.d.s.s.o.c.as.configuration.support.client.CustomClientCredentialsTokenEndpointFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * Description: 自定义的 {@link AuthenticationProvider}
 *
 * @author LiKe
 * @version 1.0.0
 * @date 2020-06-22 14:19
 */
@Slf4j
@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {

    private PasswordEncoder passwordEncoder;

    private UserDetailsService userDetailsService;

    /**
     * Description: 基于用户名和密码的认证方法<br>
     * Details: <br>
     * ☀ 如果是密码授权模式: 用户端认证的调用时机晚于
     * {@link org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter(ServletRequest, ServletResponse, FilterChain)}.<br>
     * ☀ 如果是授权码模式: 先执行用户端认证, 再执行客户端认证. 期间会清空 {@link org.springframework.security.core.context.SecurityContext}.
     *
     * @param authentication {@link Authentication} 的实现类
     * @return org.springframework.security.core.Authentication
     * @author LiKe
     * @date 2020-08-06 13:01:13
     * @see CustomClientCredentialsTokenEndpointFilter
     * @see AuthenticationProvider#authenticate(Authentication)
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        final String username = authentication.getName();
        final String password = authentication.getCredentials().toString();

        final UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (passwordEncoder.matches(password, userDetails.getPassword())) {
            return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
        }

        throw new BadCredentialsException("User's password is not correct!");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    // ~ Autowired
    // -----------------------------------------------------------------------------------------------------------------

    @Autowired
    public void setPasswordEncoder(@Qualifier("passwordEncoder") PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Autowired
    public void setUserDetailsService(@Qualifier("customUserDetailsService") UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

}
